package com.opencee.cloud.autoconfigure.utils;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.opencee.common.model.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.io.File;
import java.util.List;
import java.util.Map;

/**
 * 自定义RestTemplate请求工具类
 *
 * @author: liuyadu
 */
@Slf4j
public class RestTemplateUtil {
    private ObjectMapper objectMapper = new ObjectMapper();
    private RestTemplate restTemplate;
    private LoadBalancerClient loadBalancerClient;

    public RestTemplateUtil(RestTemplate restTemplate, LoadBalancerClient loadBalancerClient) {
        this.restTemplate = restTemplate;
        this.loadBalancerClient = loadBalancerClient;
    }

    /**
     * post 请求
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  参数
     * @param tClass  返回类型
     * @param <T>
     * @return
     */
    public <T> T post(String url, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        return restTemplate.postForObject(url, getHttpEntity(headers, params), tClass);
    }

    /**
     * post 请求
     * 返回List结果
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  参数
     * @param tClass  返回类型
     * @return List<T>
     */
    public <T> List<T> postForList(String url, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        return restTemplate.exchange(url, HttpMethod.POST, getHttpEntity(headers, params), getListReference(tClass)).getBody();
    }

    /**
     * post json请求
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param body    请求内容
     * @param tClass  返回类型
     * @param <T>
     * @return
     */
    public <T> T postJson(String url, Map<String, String> headers, Object body, Class<T> tClass) {
        MultiValueMap<String, String> headersMap = new LinkedMultiValueMap<>();
        if (headers != null) {
            headersMap.setAll(headers);
        }
        headersMap.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        return restTemplate.postForObject(url, new HttpEntity<>(JSONObject.toJSONString(body), headersMap), tClass);
    }

    /**
     * post json请求
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param body    请求内容
     * @param tClass  返回类型
     * @param <T>
     * @return List<T>
     */
    public <T> List<T> postJsonForList(String url, Map<String, String> headers, Object body, Class<T> tClass) {
        MultiValueMap<String, String> headersMap = new LinkedMultiValueMap<>();
        if (headers != null) {
            headersMap.setAll(headers);
        }
        headersMap.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        return restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(JSONObject.toJSONString(body), headersMap), getListReference(tClass)).getBody();
    }

    /**
     * get请求
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  请求内容
     * @param tClass  返回类型
     * @param <T>
     * @return
     */
    public <T> T get(String url, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        return restTemplate.exchange(url, HttpMethod.GET, getHttpEntity(headers, params), tClass).getBody();
    }

    /**
     * get请求
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  请求内容
     * @param tClass  返回类型
     * @param <T>
     * @return List<T>
     */
    public <T> List<T> getForList(String url, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        return restTemplate.exchange(url, HttpMethod.GET, getHttpEntity(headers, params), getListReference(tClass)).getBody();
    }

    /**
     * post服务请求
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param headers   请求头
     * @param params    参数
     * @param tClass    返回类型
     * @param <T>
     * @return
     */
    public <T> T postService(String serviceId, String path, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return post(url, headers, params, tClass);
    }

    /**
     * post服务请求
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param headers   请求头
     * @param params    参数
     * @param tClass    返回类型
     * @param <T>
     * @return List<T>
     */
    public <T> List<T> postServiceForList(String serviceId, String path, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return postForList(url, headers, params, tClass);
    }

    /**
     * postJSON服务请求
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param headers   请求头
     * @param body      参数
     * @param tClass    返回类型
     * @param <T>
     * @return
     */
    public <T> T postJsonService(String serviceId, String path, Map<String, String> headers, Object body, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return postJson(url, headers, body, tClass);
    }


    /**
     * postJSON服务请求
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param headers   请求头
     * @param body      参数
     * @param tClass    返回类型
     * @param <T>
     * @return List<T>
     */
    public <T> List<T> postJsonServiceForList(String serviceId, String path, Map<String, String> headers, Object body, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return postJsonForList(url, headers, body, tClass);
    }

    /**
     * get服务请求
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param tClass    返回类型
     * @param <T>
     * @return
     */
    public <T> T getService(String serviceId, String path, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return get(url, headers, params, tClass);
    }

    /**
     * get服务请求
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param tClass    返回类型
     * @param <T>
     * @return List<T>
     */
    public <T> List<T> getServiceForList(String serviceId, String path, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return getForList(url, headers, params, tClass);
    }

    /**
     * get请求返回 Result中的泛型
     *
     * @param url     请求地址
     * @param headers
     * @param params
     * @param tClass  返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<T> getForResult(String url, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        return restTemplate.exchange(url, HttpMethod.GET, getHttpEntity(headers, params), getResultReference(tClass)).getBody();
    }

    /**
     * get请求返回 Result中的泛型
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  请求参数
     * @param tClass  返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<List<T>> getForResultList(String url, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        return restTemplate.exchange(url, HttpMethod.GET, getHttpEntity(headers, params), getResultListReference(tClass)).getBody();
    }

    /**
     * get请求返回 Result中的泛型
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param headers   请求头
     * @param params    请求参数
     * @param tClass    返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<T> getServiceForResult(String serviceId, String path, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return getForResult(url, headers, params, tClass);
    }

    /**
     * get请求返回 Result中的泛型
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param headers   请求头
     * @param params    请求参数
     * @param tClass    返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<List<T>> getServiceForResultList(String serviceId, String path, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return getForResultList(url, headers, params, tClass);
    }

    /**
     * post 请求返回 Result中的泛型
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  参数
     * @param tClass  返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<T> postForResult(String url, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        return restTemplate.exchange(url, HttpMethod.POST, getHttpEntity(headers, params), getResultReference(tClass)).getBody();
    }

    /**
     * post 请求返回 Result中的泛型
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  参数
     * @param tClass  返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<List<T>> postForResultList(String url, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        return restTemplate.exchange(url, HttpMethod.POST, getHttpEntity(headers, params), getResultListReference(tClass)).getBody();
    }


    /**
     * post 请求返回 Result中的泛型
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param headers   请求头
     * @param params    参数
     * @param tClass    返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<T> postServiceForResult(String serviceId, String path, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return postForResult(url, headers, params, tClass);
    }

    /**
     * post 请求返回 Result中的泛型
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param headers   请求头
     * @param params    参数
     * @param tClass    返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<List<T>> postServiceForResultList(String serviceId, String path, Map<String, String> headers, Map<String, String> params, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return postForResultList(url, headers, params, tClass);
    }

    /**
     * post json 请求返回 Result中的泛型
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param body    请求内容
     * @param tClass  返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<T> postJsonForResult(String url, Map<String, String> headers, Object body, Class<T> tClass) {
        MultiValueMap<String, String> headersMap = new LinkedMultiValueMap<>();
        if (headers != null) {
            headersMap.setAll(headers);
        }
        headersMap.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        return restTemplate.exchange(url, HttpMethod.POST, new HttpEntity(JSONObject.toJSONString(body)), getResultReference(tClass)).getBody();
    }

    /**
     * post json 请求返回 Result中的泛型
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param body    请求内容
     * @param tClass  返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<List<T>> postJsonForResultList(String url, Map<String, String> headers, Object body, Class<T> tClass) {
        MultiValueMap<String, String> headersMap = new LinkedMultiValueMap<>();
        if (headers != null) {
            headersMap.setAll(headers);
        }
        headersMap.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        return restTemplate.exchange(url, HttpMethod.POST, new HttpEntity(JSONObject.toJSONString(body)), getResultListReference(tClass)).getBody();
    }

    /**
     * post json 请求返回 Result中的泛型
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param headers   请求头
     * @param body      请求内容
     * @param tClass    返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<T> postJsonServiceForResult(String serviceId, String path, Map<String, String> headers, Object body, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return postJsonForResult(url, headers, body, tClass);
    }

    /**
     * post json 请求返回 Result中的泛型
     *
     * @param serviceId 服务id > spring.application.name
     * @param path      请求地址
     * @param headers   请求头
     * @param body      请求内容
     * @param tClass    返回类型
     * @param <T>
     * @return
     */
    public <T> ApiResult<List<T>> postJsonServiceForResultList(String serviceId, String path, Map<String, String> headers, Object body, Class<T> tClass) {
        String url = String.format("%s%s", getServiceInstance(serviceId), path);
        return postJsonForResultList(url, headers, body, tClass);
    }

    /**
     * 文件上传
     *
     * @param file      文件
     * @param paramName 参数名称:file或image
     * @param uploadUrl 上传地址
     * @param tClass    返回类型
     * @return
     */
    public <T> T upload(File file, String paramName, String uploadUrl, Class<T> tClass) {
        if (file != null && !file.exists()) {
            return null;
        }
        //将文件传入文件管理系统
        FileSystemResource resource = new FileSystemResource(file);
        MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
        param.add(paramName, resource);
        ResponseEntity<T> responseEntity = restTemplate.postForEntity(uploadUrl, param, tClass);
        //删除本地文件
        file.delete();
        return responseEntity.getBody();
    }

    /**
     * 获取服务实例
     *
     * @param serviceId
     * @return
     */
    public String getServiceInstance(String serviceId) {
        ServiceInstance serviceInstance = loadBalancerClient.choose(serviceId);
        // 查询服务实例
        if (serviceInstance == null) {
            throw new RuntimeException(String.format("未找到服务实例:%s", serviceId));
        }
        return serviceInstance.getUri().toString();
    }

    /**
     * 封装请求参数
     *
     * @param headers
     * @param params
     * @return
     */
    public HttpEntity getHttpEntity(Map<String, String> headers, Map<String, String> params) {
        //header信息，包括了http basic认证信息
        MultiValueMap<String, String> headersMap = new LinkedMultiValueMap<>();
        if (headers != null) {
            headersMap.setAll(headers);
        }
        //body请求体部分
        MultiValueMap<String, String> bodyMap = new LinkedMultiValueMap<>();
        if (params != null) {
            bodyMap.setAll(params);
        }
        //merge成为一个HttpEntity
        return new HttpEntity<>(bodyMap, headersMap);
    }


    /**
     * 返回List泛型
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> ParameterizedTypeReference<List<T>> getListReference(Class<T> clazz) {
        JavaType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, clazz);
        return ParameterizedTypeReference.forType(javaType);
    }

    /**
     * 获取泛型
     *
     * @param clazz
     * @param <T>
     * @return
     */
    private <T> ParameterizedTypeReference<ApiResult<T>> getResultReference(Class<T> clazz) {
        //objectMapper已经缓存Type，不需要额外缓存
        JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ApiResult.class, ApiResult.class, clazz);
        return ParameterizedTypeReference.forType(javaType);
    }

    /**
     * 返回List泛型
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> ParameterizedTypeReference<ApiResult<List<T>>> getResultListReference(Class<T> clazz) {
        JavaType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, clazz);
        JavaType resultType = objectMapper.getTypeFactory().constructParametricType(ApiResult.class, javaType);
        return ParameterizedTypeReference.forType(resultType);
    }


    public RestTemplate getRestTemplate() {
        return restTemplate;
    }

    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }
}
